<?php

/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#namespace tehframework\annotations;

#use \UnexpectedValueException;
#use \BadMethodCallException;
#use \ReflectionClass;
#use \ReflectionObject;
#use \ReflectionProperty;
#use \ReflectionMethod;
#use \ReflectionParameter;
#use \ReflectionExtension;
#use tehframework\annotations\builders\DocBlockBuilder;
#use tehframework\annotations\parsers\DocBlockParser;
#use tehframework\annotations\tags\UnknownTag;
#use tehframework\storage\Storage;
#use tehframework\storage\TransientStorage;
#use tehframework\testing\phpunit\TestCase;

require_once 'test/testHelper.php';

/**
 * Short description.
 * 
 * Long description.
 * 
 * @author  morkai <lukasz@walukiewicz.eu>
 * @unknown
 * 
 * @Fake<MoreFake>("foo")
 * @FooBar
 */
class DocBlockFactoryTest extends TestCase
{
	/**
	 * @var DocBlockFactory
	 */
	private $factory;
	
	/**
	 * @var DocBlockParser
	 */
	private $parser;
	
	/**
	 * @var DocBlockBuilder
	 */
	private $builder;
	
	/**
	 * @var DocBlockDefinition
	 */
	private $def;
	
	/**
	 * @var DocBlock
	 */
	private $doc;
	
	/**
	 * Short description.
	 * 
	 * Long description.
	 * 
	 * @author  morkai <lukasz@walukiewicz.eu>
	 * @unknown
	 * 
	 * @Fake<MoreFake>("foo")
	 * @FooBar
	 */
	private $test;
	
	protected function beforeTest()
	{
		$this->factory = new DocBlockFactory();
		$this->parser  = $this->mock('DocBlockParser');
		$this->builder = $this->mock('DocBlockBuilder');
		$this->def     = new DocBlockDefinition(
			'Short description.',
			'Long description.',
			array(
				new TagDefinition('author', 'morkai <lukasz@walukiewicz.eu>'),
				new TagDefinition('unknown')
			),
			array(
				new AnnotationDefinition('Fake', 'MoreFake', null, array('foo')),
				new AnnotationDefinition('FooBar')
			)
		);
		$this->doc     = new DocBlock(
			'Short description.',
			'Long description.',
			array(
				'author'  => array(new AuthorTag($this->def->tagDefinitions[0])),
				'unknown' => array(new UnknownTag($this->def->tagDefinitions[1]))
			),
			array(
				'Fake'   => array(new MoreFakeAnnotation('foo')),
				'FooBar' => array(new FooBarAnnotation())
			)
		);
	}
	
	function testGetDocBlockThrowsExceptionIfTargetDoesNotHaveGetDocCommentMethod()
	{
		$this->setExpectedException('UnexpectedValueException');
		
		$this->factory
			->setParser($this->parser)
			->setBuilder($this->builder)
			->getDocBlock(new ReflectionExtension('Reflection'));
	}
	
	function testGetDocBlockThrowsExceptionIfParserWasNotSpecified()
	{
		$this->setExpectedException('BadMethodCallException');
		
		$this->factory
			->setBuilder($this->builder)
			->getDocBlock(new ReflectionObject($this));
	}
	
	function testGetDocBlockThrowsExceptionIfBuilderWasNotSpecified()
	{
		$this->setExpectedException('BadMethodCallException');
		
		$this->factory
			->setParser($this->parser)
			->getDocBlock(new ReflectionObject($this));
	}
	
	function testGetDocBlockReturnsCorrectDocBlockForReflectionClass()
	{
		$ref = new ReflectionClass(__CLASS__);
		
		$this->expect
			->one($this->parser)
				->parseDocBlock($ref->getDocComment())->returns($this->def)
			->one($this->builder)
				->buildDocBlock($this->def)->returns($this->doc);
		
		$this->factory->setParser($this->parser)->setBuilder($this->builder);
		
		$this->assertEquals($this->doc, $this->factory->getDocBlock($ref));
	}
	
	function testGetDocBlockReturnsCorrectDocBlockForReflectionObject()
	{
		$ref = new ReflectionObject($this);
		
		$this->expect
			->one($this->parser)
				->parseDocBlock($ref->getDocComment())->returns($this->def)
			->one($this->builder)
				->buildDocBlock($this->def)->returns($this->doc);
		
		$this->factory->setParser($this->parser)->setBuilder($this->builder);
		
		$this->assertEquals($this->doc, $this->factory->getDocBlock($ref));
	}
	
	function testGetDocBlockReturnsCorrectDocBlockForReflectionProperty()
	{
		$ref = new ReflectionProperty($this, 'test');
		
		$this->expect
			->one($this->parser)
				->parseDocBlock($ref->getDocComment())->returns($this->def)
			->one($this->builder)
				->buildDocBlock($this->def)->returns($this->doc);
		
		$this->factory->setParser($this->parser)->setBuilder($this->builder);
		
		$this->assertEquals($this->doc, $this->factory->getDocBlock($ref));
	}
	
	/**
	 * Short description.
	 * 
	 * Long description.
	 * 
	 * @author  morkai <lukasz@walukiewicz.eu>
	 * @unknown
	 * 
	 * @Fake<MoreFake>("foo")
	 * @FooBar
	 */
	function testGetDocBlockReturnsCorrectDocBlockForReflectionMethod()
	{
		$ref = new ReflectionMethod($this, __FUNCTION__);
		
		$this->expect
			->one($this->parser)
				->parseDocBlock($ref->getDocComment())->returns($this->def)
			->one($this->builder)
				->buildDocBlock($this->def)->returns($this->doc);
		
		$this->factory->setParser($this->parser)->setBuilder($this->builder);
		
		$this->assertEquals($this->doc, $this->factory->getDocBlock($ref));
	}
	
	/**
	 * @param  string? $test
	 * 
	 * @Fake[test]
	 * @Fake[test]("foo")
	 * @Fake[foo]
	 */
	function testGetDocBlockReturnsCorrectDocBlockForReflectionParameter($test = null)
	{
		$ref = new ReflectionParameter(array($this, __FUNCTION__), 0);
		
		$def = new DocBlockDefinition(null, null,
			array(new TagDefinition('param', 'string? $test')),
			array(
				new AnnotationDefinition('Fake', null, 'test'),
				new AnnotationDefinition('Fake', null, 'test', array('foo')),
				new AnnotationDefinition('Fake', null, 'foo')
			)
		);
		
		$doc = new DocBlock(null, null,
			array('param' => array(new UnknownTag($def->tagDefinitions[0]))),
			array('Fake' => array(new FakeAnnotation(), new FakeAnnotation('foo')))
		);
		
		$this->expect
			->one($this->parser)
				->parseDocBlock($ref->getDeclaringFunction()->getDocComment())
				->returns($def)
			->one($this->builder)
				->buildDocBlock($def)->returns($doc);
		
		$this->factory->setParser($this->parser)->setBuilder($this->builder);
		
		$this->assertEquals($doc, $this->factory->getDocBlock($ref));
	}
	
	/** @MoreFake */
	function testGetDocBlockSuppliesTargetToBuilderIfItIsAwareOfIt()
	{
		$ref = new ReflectionMethod($this, __FUNCTION__);
		
		$def = new DocBlockDefinition(null, null, array(), array(
			new AnnotationDefinition('MoreFake')
		));
		
		$ann = new MoreFakeAnnotation();
		
		$doc = new DocBlock(null, null, array(), array(
			'MoreFake' => array($ann)
		));
		
		$annBuilder = $this->mock('AnnotationBuilder');
		
		$builder = new DocBlockBuilderImpl();
		$builder->setAnnotationBuilder($annBuilder);
		
		$this->expect
			->one($this->parser)
				->parseDocBlock($ref->getDocComment())->returns($def)
			->one($annBuilder)
				->buildAnnotation($def->annotationDefinitions[0])->returns($ann);
		
		$this->factory->setParser($this->parser)->setBuilder($builder);
		
		$docBlock = $this->factory
			->setParser($this->parser)
			->setBuilder($builder)
			->getDocBlock($ref);
		
		$this->assertEquals($doc, $docBlock);
		$this->assertTrue($docBlock->getAnnotation('MoreFake')->isAwareOfTarget());
	}
	
	function testGetDocBlockStoresDocBlocksInGivenStorage()
	{
		$ref = new ReflectionClass(__CLASS__);
		
		$storage = $this->mock('Storage');
		
		$this->expect
			->one($this->parser)
				->parseDocBlock($ref->getDocComment())->returns($this->def)
			->one($this->builder)
				->buildDocBlock($this->def)->returns($this->doc)
			->one($storage)
				->store(Yay::any(), $this->doc)->returns($storage)
			->allowing($storage)
				->restore(Yay::any())->returns(null);
		
		$this->factory
			->setStorage($storage)
			->setParser($this->parser)
			->setBuilder($this->builder)
			->getDocBlock($ref);
	}
	
	function testGetDocBlockRestoresStoredDocBlocksFromGivenStorage()
	{
		$ref = new ReflectionClass(__CLASS__);
		
		$storage = new TransientStorage();
		
		$this->expect
			->one($this->parser)
				->parseDocBlock($ref->getDocComment())->returns($this->def)
			->one($this->builder)
				->buildDocBlock($this->def)->returns($this->doc);
		
		$this->factory
			->setStorage($storage)
			->setParser($this->parser)
			->setBuilder($this->builder);
		
		$this->assertSame(0, count($storage->getDataMap()));
		
		$this->factory->getDocBlock($ref);
		$this->assertSame(1, count($storage->getDataMap()));
		
		$this->factory->getDocBlock($ref);
		$this->assertSame(1, count($storage->getDataMap()));
	}
	
	function testGetDocBlockReturnsEmptyDocBlockIfTargetDoesNotHaveDocComment()
	{
		$ref = new ReflectionMethod(__CLASS__, __FUNCTION__);
		
		$this->expect->never($this->parser)->never($this->builder);
		
		$this->factory->setParser($this->parser)->setBuilder($this->builder);
		
		$this->assertEquals(new DocBlock(), $this->factory->getDocBlock($ref));
	}
}